﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using DeltaGroup.WheelOfJeopardy.Model;
using DeltaGroup.WheelOfJeopardy.UI.Events;

namespace DeltaGroup.WheelOfJeopardy.GameManager
{
    public class NormalRound : Round, INotifyPropertyChanged
    {
        private readonly IGameSettings _settings;
        private readonly DifficultyLevel _difficulty;

        #region Fields

        private int _spinsRemaining;

        #endregion Fields

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref = "NormalRound" /> class.
        /// </summary>
        /// <param name="settings">game settings</param>
        /// <param name = "categories">The categories.</param>
        /// <param name = "contestants">The contestants.</param>
        /// <param name="difficulty">Game Difficulty</param>
        public NormalRound(IGameSettings settings, List<Category> categories, IEnumerable<Contestant> contestants, DifficultyLevel difficulty)
        {
            _settings = settings;
            _difficulty = difficulty;
            Contestants = new Queue<Contestant>(contestants);
            Categories = categories;

            SectorHandlers = new Dictionary<Type, Action<Sector>>
                                 {
                                     {typeof (CategorySector), HandleCategorySector},
                                     {typeof (LoseTurnSector), HandleLoseTurnSector},
                                     {typeof (FreeTurnSector), HandleFreeTurnSector},
                                     {typeof (BankruptSector), HandleBankruptSector},
                                     {typeof (SpinAgainSector), HandleSpinAgainSector},
                                     {typeof (PlayersChoiceSector), HandlePlayersChoiceSector},
                                     {typeof (OpponentsChoiceSector), HandleOpponentsChoiceSector}
                                 };

            MaximumRoundPoint = categories.SelectMany(x => x.Challenges).Max(x => x.Point);

            Finished += DettachEvents;
        }

        #endregion Constructors

        #region Events

        /// <summary>
        ///     Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion Events

        #region Properties

        /// <summary>
        ///     Gets or sets the categories.
        /// </summary>
        /// <value>The categories.</value>
        public List<Category> Categories { get; private set; }

        /// <summary>
        ///     Gets or sets the challenge manager.
        /// </summary>
        /// <value>The challenge manager.</value>
        public IChallengeManager ChallengeManager { get; set; }

        /// <summary>
        ///     Gets or sets the contestants.
        /// </summary>
        /// <value>The contestants.</value>
        private Queue<Contestant> Contestants { get; set; }

        /// <summary>
        ///     Gets or sets the current challenge.
        /// </summary>
        /// <value>The current challenge.</value>
        private Challenge CurrentChallenge { get; set; }

        /// <summary>
        ///     Gets the current contestant.
        /// </summary>
        /// <value>The current contestant.</value>
        private Contestant CurrentContestant
        {
            get { return Contestants.Peek(); }
        }

        /// <summary>
        /// Maximum Challenge Point for this round
        /// </summary>
        private int MaximumRoundPoint { get; set; }

        /// <summary>
        ///     Gets or sets the spins remaining.
        /// </summary>
        /// <value>The spins remaining.</value>
        public int SpinsRemaining
        {
            get { return _spinsRemaining; }
            set
            {
                _spinsRemaining = value;
                FirePropertyChanged("SpinsRemaining");
            }
        }

        /// <summary>
        ///     Gets or sets the UI manager.
        /// </summary>
        /// <value>The UI manager.</value>
        public INormalRoundUIManager UIManager { get; set; }

        /// <summary>
        ///     Gets a value indicating whether this instance has no more challenges.
        /// </summary>
        /// <value>
        ///     <c>true</c> if this instance has no more challenges; otherwise, <c>false</c>.
        /// </value>
        private bool HasNoMoreChallenges
        {
            get { return Categories.SelectMany(x => x.Challenges).All(x => x.IsChallenged); }
        }

        /// <summary>
        ///     Gets a value indicating whether this instance is end of round.
        /// </summary>
        /// <value>
        ///     <c>true</c> if this instance is end of round; otherwise, <c>false</c>.
        /// </value>
        private bool IsEndOfRound
        {
            get { return SpinsRemaining <= 0; }
        }

        /// <summary>
        ///     Gets or sets the sector handlers.
        /// </summary>
        /// <value>The sector handlers.</value>
        private Dictionary<Type, Action<Sector>> SectorHandlers { get; set; }

        /// <summary>
        ///     Gets the unfinished categories.
        /// </summary>
        /// <value>The unfinished categories.</value>
        private List<Category> UnfinishedCategories
        {
            get { return Categories.Where(x => x.Challenges.Any(y => y.IsChallenged == false)).ToList(); }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        ///     Starts this Round.
        /// </summary>
        public override void Start()
        {
            UIManager.CategorySelected += HandleCategorySelected;
            UIManager.WagerReceived += HandleWager;
            UIManager.ChallengeResponseReceived += HandleResponse;
            UIManager.ResponseTimerExpired += HandleTimerExpiration;
            UIManager.UseFreeTurn += HandleUseFreeTurn;
            UIManager.NotUseFreeTurn += HandleNotUseFreeTurn;
            UIManager.SectorSelected += HandleSector;

            SpinsRemaining = _settings.SpinsPerNormalRound;
            CurrentContestant.IsPlaying = true;

            UIManager.ShowMessage(string.Format("Let's get this game started. {0}, you are first!", CurrentContestant.Name));
            SpinWheel();
        }

        /// <summary>
        ///     Completes the challenge.
        /// </summary>
        private void CompleteChallenge()
        {
            CurrentChallenge.IsChallenged = true;
            if (CurrentChallenge is DailyDouble)
            {
                UIManager.ShowMessage(string.Format("Daily Double! {0} make a wager!", CurrentContestant.Name));

                var roundMax = MaximumRoundPoint > CurrentContestant.Score ? MaximumRoundPoint : CurrentContestant.Score;

                UIManager.PromptForWager(CurrentChallenge.Category, roundMax);
            }
            else
            {
                UIManager.ShowMessage(string.Format("{0} here is your challenge, {1} seconds go go!", CurrentContestant.Name, _settings.GetResponseTimeLimit(_difficulty)));
                UIManager.PromptForResponse(CurrentChallenge, _settings.GetResponseTimeLimit(_difficulty));
            }
        }

        /// <summary>
        ///     Dettaches the events.
        /// </summary>
        /// <param name = "sender">The sender.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void DettachEvents(object sender, EventArgs e)
        {
            UIManager.CategorySelected += HandleCategorySelected;
            UIManager.WagerReceived -= HandleWager;
            UIManager.ChallengeResponseReceived -= HandleResponse;
            UIManager.ResponseTimerExpired -= HandleTimerExpiration;
            UIManager.UseFreeTurn -= HandleUseFreeTurn;
            UIManager.NotUseFreeTurn -= HandleNotUseFreeTurn;
            UIManager.SectorSelected -= HandleSector;
        }

        /// <summary>
        ///     Fires the property changed.
        /// </summary>
        /// <param name = "name">The name.</param>
        private void FirePropertyChanged(string name)
        {
            if (PropertyChanged == null)
                return;

            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        /// <summary>
        ///     Gets the first unchallenged challenge.
        /// </summary>
        /// <param name = "category">The category.</param>
        /// <returns></returns>
        private Challenge GetFirstUnchallengedChallenge(string category)
        {
            return Categories.First(x => x.Title == category).Challenges.OrderBy(x => x.Point).FirstOrDefault(x => x.IsChallenged == false);
        }

        /// <summary>
        ///     Handles the bankrupt sector.
        /// </summary>
        /// <param name = "sector">The sector.</param>
        private void HandleBankruptSector(Sector sector)
        {
            if(CurrentContestant.Score > 0)
                CurrentContestant.Score = 0;
            UIManager.ShowMessage(string.Format("Bankrupt! {0} lost all his points.", CurrentContestant.Name));
            NextContestant();
        }

        /// <summary>
        ///     Handles the category sector.
        /// </summary>
        /// <param name = "sector">The sector.</param>
        private void HandleCategorySector(Sector sector)
        {
            var category = ((CategorySector) sector).Category;

            CurrentChallenge = GetFirstUnchallengedChallenge(category);

            if (CurrentChallenge == null)
            {
                UIManager.ShowMessage(string.Format("No more challenge in [{0}], Spin Again!", category));
                SpinWheel();
            }
            else
                CompleteChallenge();
        }

        /// <summary>
        ///     Handles the category selected.
        /// </summary>
        /// <param name = "sender">The sender.</param>
        /// <param name = "e">The <see cref = "DeltaGroup.WheelOfJeopardy.UI.Events.CategorySelectedEventArgs" /> instance containing the event data.</param>
        private void HandleCategorySelected(object sender, CategorySelectedEventArgs e)
        {
            CurrentChallenge = GetFirstUnchallengedChallenge(e.Category.Title);
            if (CurrentChallenge != null)
                CompleteChallenge();
        }

        /// <summary>
        ///     Handles the free turn sector.
        /// </summary>
        /// <param name = "sector">The sector.</param>
        private void HandleFreeTurnSector(Sector sector)
        {
            CurrentContestant.FreeTurnCount++;

            UIManager.ShowMessage(string.Format("Free Turn awarded to {0}! Go Again!", CurrentContestant.Name));

            SpinWheel();
        }

        /// <summary>
        ///     Handles the lose turn sector.
        /// </summary>
        /// <param name = "sector">The sector.</param>
        private void HandleLoseTurnSector(Sector sector)
        {
            UIManager.ShowMessage(string.Format("{0} has hit a Lose Turn!", CurrentContestant.Name));

            NextContestant();
        }

        /// <summary>
        ///     Handles the not use free turn.
        /// </summary>
        /// <param name = "sender">The sender.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void HandleNotUseFreeTurn(object sender, EventArgs e)
        {
            UIManager.ShowMessage(string.Format("{0} has decided not to use a Free Turn.", CurrentContestant.Name));

            MoveToNextContestant();
        }

        /// <summary>
        ///     Handles the opponents choice sector.
        /// </summary>
        /// <param name = "sector">The sector.</param>
        private void HandleOpponentsChoiceSector(Sector sector)
        {
            UIManager.ShowMessage(string.Format("Opponent's Choice! Pick a category for {0}", CurrentContestant.Name));
            UIManager.PromptToSelectCategory(UnfinishedCategories);
        }

        /// <summary>
        ///     Handles the players choice sector.
        /// </summary>
        /// <param name = "sector">The sector.</param>
        private void HandlePlayersChoiceSector(Sector sector)
        {
            UIManager.ShowMessage(string.Format("Player's Choice! {0}, pick a category for yourself!", CurrentContestant.Name));
            UIManager.PromptToSelectCategory(UnfinishedCategories);
        }

        /// <summary>
        ///     Handles the response.
        /// </summary>
        /// <param name = "sender">The sender.</param>
        /// <param name = "e">The <see cref = "ChallengeResponseReceivedEventArgs" /> instance containing the event data.</param>
        private void HandleResponse(object sender, ChallengeResponseReceivedEventArgs e)
        {
            var response = e.Response;

            var point = CurrentChallenge.Point;

            if (ChallengeManager.IsResponseCorrect(_difficulty, CurrentChallenge, response))
            {
                CurrentContestant.Score += point;
                CurrentChallenge = null;

                UIManager.ShowMessage(string.Format("Correct!! {0} has been awarded {1} points! Go Again!",
                                                    CurrentContestant.Name, point));
                SpinWheel();
            }
            else
            {
                UIManager.ShowMessage(string.Format("Incorrect!! {0} has lost {1} points!", CurrentContestant.Name, point));
                UIManager.ShowMessage(string.Format("The correct answer was {0}", CurrentChallenge.ExpectedResponse));

                CurrentContestant.Score -= point;

                CurrentChallenge = null;
                NextContestant();
            }
        }

        /// <summary>
        ///     Handles the sector.
        /// </summary>
        /// <param name = "sender">The sender.</param>
        /// <param name = "e">The <see cref = "SectorSelectedEventArgs" /> instance containing the event data.</param>
        private void HandleSector(object sender, SectorSelectedEventArgs e)
        {
            SpinsRemaining--;

            var sector = e.SelectedSector;
            var sectorType = sector.GetType();

            if (SectorHandlers.ContainsKey(sectorType))
                SectorHandlers[sectorType](sector);
            else
                throw new Exception("Unknown Sector: " + sectorType.AssemblyQualifiedName);
        }

        /// <summary>
        ///     Handles the spin again sector.
        /// </summary>
        /// <param name = "sector">The sector.</param>
        private void HandleSpinAgainSector(Sector sector)
        {
            UIManager.ShowMessage(string.Format("{0} got a Spin Again sector! Spin Again!!", CurrentContestant.Name));
            SpinWheel();
        }

        /// <summary>
        ///     Handles the timer expiration.
        /// </summary>
        /// <param name = "sender">The sender.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void HandleTimerExpiration(object sender, EventArgs e)
        {
            UIManager.ShowMessage(string.Format("No response was received from {0} within the time limit", CurrentContestant.Name));
            NextContestant();
        }

        /// <summary>
        ///     Handles the use free turn.
        /// </summary>
        /// <param name = "sender">The sender.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void HandleUseFreeTurn(object sender, EventArgs e)
        {
            CurrentContestant.FreeTurnCount--;

            UIManager.ShowMessage(string.Format("{0} used Free Turn credit", CurrentContestant.Name));
            SpinWheel();
        }

        /// <summary>
        ///     Handles the wager.
        /// </summary>
        /// <param name = "sender">The sender.</param>
        /// <param name = "e">The <see cref = "DeltaGroup.WheelOfJeopardy.UI.Events.WagerReceivedEventArgs" /> instance containing the event data.</param>
        private void HandleWager(object sender, WagerReceivedEventArgs e)
        {
            CurrentChallenge.Point = e.Wager;

            UIManager.PromptForResponse(CurrentChallenge, _settings.GetResponseTimeLimit(_difficulty));
        }

        /// <summary>
        ///     Moves to next contestant.
        /// </summary>
        private void MoveToNextContestant()
        {
            var current = Contestants.Dequeue();
            current.IsPlaying = false;
            Contestants.Enqueue(current);
            CurrentContestant.IsPlaying = true;

            UIManager.ShowMessage(string.Format("Go ahead {0} it is your turn to spin!", CurrentContestant.Name));
            
            SpinWheel();
        }

        /// <summary>
        ///     Nexts the contestant.
        /// </summary>
        private void NextContestant()
        {
            if (CurrentContestant.FreeTurnCount > 0)
            {
                UIManager.PromptForFreeTurnUsage(CurrentContestant.FreeTurnCount);
            }
            else
            {
                MoveToNextContestant();
            }
        }

        /// <summary>
        ///     Spins the wheel.
        /// </summary>
        private void SpinWheel()
        {
            if (IsEndOfRound || HasNoMoreChallenges)
            {
                foreach (var contestant in Contestants)
                {
                    contestant.IsPlaying = false;
                }

                RaiseRoundFinished();
            }
            else
            {
                UIManager.PromptToSpin();
            }
        }

        #endregion Methods
    }
}